package bsh;


import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.TypeWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * Compiler para um LDC
 * 
 * @author takeshi
 */
public class LiteralCompiler implements NodeCompiler {

    private final BSHLiteral literal;

    private final TypeWrapper type;

    private final ReflectionUtils reflector;

    private final Object value;

    /**
     * @param literal BSHLiteral
     * @param reflector reflector
     */
    public LiteralCompiler(BSHLiteral literal, ReflectionUtils reflector) {
        super();
        this.reflector = reflector;
        this.literal = literal;
        this.value = Primitive.unwrap(literal.value);
        this.type = this.findType();
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        state.addLineNumber(host, this.literal.getLineNumber());
        int oldStack = state.getStackSize();
        if (this.value == null) {
            host.visitInsn(Opcodes.ACONST_NULL);
            state.pushStack(TypeWrapper.NULL_TYPE_WRAPPER);
        } else if (this.reflector.isBooleanPrimitive(this.type)) {
            BytecodeHelper.pushBooleanValue(Boolean.TRUE.equals(this.value), host);
            state.pushStack(boolean.class);
        } else if (this.reflector.isIntPrimitive(this.type)) {
            if(this.type.equals(JavaClassWrapper.CHAR_WRAPPER)) {
                int charValue = ((Character) this.value).charValue();
                BytecodeHelper.pushIntConst(charValue, host);
                state.pushStack(this.type);
            } else {
                int intValue = ((Number) this.value).intValue();
                BytecodeHelper.pushIntConst(intValue, host);
                state.pushStack(this.type);
            }
        } else if (this.type.equals(JavaClassWrapper.LONG_WRAPPER)) {
            BytecodeHelper.pushLongConst(((Number) this.value).longValue(), host);
            state.pushStack(this.type);
        } else if (this.type.equals(JavaClassWrapper.FLOAT_WRAPPER)) {
            BytecodeHelper.pushFloatConst(((Number) this.value).floatValue(), host);
            state.pushStack(this.type);
        } else if (this.type.equals(JavaClassWrapper.DOUBLE_WRAPPER)) {
            BytecodeHelper.pushDoubleConst(((Number) this.value).doubleValue(), host);
            state.pushStack(this.type);
        } else {
            // either string or class const
            host.visitLdcInsn(this.value);
            state.pushStack(this.type);
        }
        state.assertStackSize(oldStack + 1);
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(Scope scope) {
        return this.type;
    }

    /**
     * @return {@link TypeWrapper}
     */
    private TypeWrapper findType() {
        Object val = this.literal.value;
        if (val == null) {
            return TypeWrapper.NULL_TYPE_WRAPPER;
        }
        Object object = Primitive.unwrap(val);
        if (object == null) {
            return TypeWrapper.NULL_TYPE_WRAPPER;
        }
        if (Primitive.isWrapperType(object.getClass())) {
            // unwrap it
            return JavaClassWrapper.wrapperFor(Primitive.unboxType(object.getClass()));
        }
        return JavaClassWrapper.wrapperFor(object.getClass());
    }

}
